<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style media="screen">
        body {background: #000;}
        #svg {background: #fff;}
    </style>
</head>
<body>
    <svg id="svg" width="800" height="800">
    </svg>
    <script>
        //数据
        let data = [{name:'数据1',num:500}, {name:'数据2',num:200}, {name:'数据3',num:300}, {name:'数据4',num:50}];
        let svg = document.getElementById('svg');   // 获取元素
        let sw = svg.getAttribute('width');
        let sh = svg.getAttribute('height');
        let r = 150;     // 半径
        let max = 0;     // 求和的变量
        let s = 0;     // 起始角度
        let cx = 400,cy = 400;     // 圆心的位置

        data.forEach((item)=>{max += item.num});    // 求和
        data.forEach((item,index)=>{
            let a = item.num / max * 360;     // 每条数据所占的角度
            let color = `rgb(${Math.floor(Math.random()*256)}, ${Math.floor(Math.random()*256)}, ${Math.floor(Math.random()*256)})`;
            setPie(s, s + a, color);     // 绘制每条数据的扇形图
            setData((s + s + a) / 2, item, color);     // 绘制指针和文字
            s += a;
        });

        function d2a(n) {
            return n * Math.PI / 180;
        }

        function getPoint(ang, r) {
            return {
                x: cx + Math.sin(d2a(ang)) * r,
                y: cy + Math.cos(d2a(ang)) * r
            }
        }

        function setPie(start, end, color) {
            let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            path.style.fill = color;
            svg.appendChild(path);
            let fnNext = null;
            let curR = r;    // 通过更改半径实现动画
            let size = 40;   // 运动速度，40次运动到达目标位置。

            calcD(r);    // 第一次绘制扇形

            function calcD(r) {    // 绘制扇形的方法
                let {x: x1, y: y1} = getPoint(start, r);   // 扇形的起始点坐标
                let {x: x2, y: y2} = getPoint(end, r);    // 扇形的终止点坐标
                if (end - start > 180) {
                    path.setAttribute('d', `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 1 0 ${x2} ${y2} Z`);
                } else {
                    path.setAttribute('d', `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 0 0 ${x2} ${y2} Z`);
                }
            }

            function move(end) {     // end参数是终点半径
                let start = curR;    // 起始半径
                let count = 0;
                let dis = end - start;     // 运动的距离
                fnNext = function() {
                    count++;   // 动画的次数
                    let a = 1 - count/size    // 动画的速率计算
                    curR = start + dis * (1-a * a * a);   // 动画的过程，起点 + 每次运动的数值
                    calcD(curR);    // 每次动画重新绘制扇形
                    if (count >= size) {    // 到达终点停止运动，next函数调用的是fnNext才会运动，fnNext为null后就不会再运动
                        fnNext = null;
                    }
                }
            }
            path.onmouseover = function() {
                move(r * 1.2);
            }
            path.onmouseout = function() {
                move(r);
            }

            next();     // 绘制完扇形之后就开始启动帧动画
            function next() {
                fnNext && fnNext();    // 鼠标移入扇形才会把函数赋值给fnNext，动画运动完成是null
                requestAnimationFrame(next);
            }
        }

        function getLine(x, y) {
            return (y - cy) / (x - cx);
        }

        function setData(arg, item, color) {
            let len = 30, lang = 20, font = 20;   // 分别是指针的长度，延伸的长度，字号
            let x1,x2,y1,txtX,txtY;
            let txtW = item.name.length * font;    // 计算文字元素的宽度
            let {x: x, y: y} = getPoint(arg, r);    // 指针起点的坐标
            // 指针和文字的元素的样式和内容
            let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            let txt = document.createElementNS('http://www.w3.org/2000/svg', 'text');
            txt.innerHTML = item.name;
            path.style.fill = 'none';
            path.style.stroke = color;
            txt.style.fill = color;
            txt.style.fontSize = font + 'px';
            svg.appendChild(path);
            svg.appendChild(txt);

            if (x <= cx) {     // 文字在饼图左边
                x1 = x - len;
                txtX = x1 - txtW - lang;
                x2 = x1 - lang;
            } else {     // 文字在饼图右边
                x1 = x + len;
                txtX = x1 + font + lang / 2;
                x2 = x1 + lang;
            }

            if (y <= cy) {     // 文字在饼图上边
                y1 = y - len;
                txtY = y1 + font / 2;
            } else {      // 文字在饼图下边
                y1 = y + len;
                txtY = y1 + font / 2;
            }
            path.setAttribute('d', `M ${x} ${y} L ${x1} ${y1} L ${x2} ${y1}`);
            txt.setAttribute('x', txtX);
            txt.setAttribute('y', txtY);
        }

    </script>
</body>
</html>
